<

アクションAPIのリビジョン

まとめ

Flutter では、Intent通常バインドされるオブジェクトです を使用してキーボードのキーの組み合わせにShortcutsウィジェット。 アンIntentにバインドできるAction、 これにより、アプリケーションの状態を更新したり、他の操作を実行したりできます。 この API を使用する過程で、いくつかの欠点が判明しました。 そのため、操作を簡単にするために Actions API を更新しました。 使って理解すること。

以前のアクション API 設計では、アクションはLocalKeyActionFactory新しいものを生み出したのはAction毎回invokeメソッドが呼び出されました。 現在の API では、アクションはIntentActionインスタンス (Map<Type, Action>)、 また、呼び出しごとに新たに作成されるわけではありません。

コンテクスト

元の Actions API 設計は、からアクションを呼び出すことを目的としていました。 ウィジェットを作成し、それらのアクションをウィジェットのコンテキストで動作させることができます。 チームはアクションを使用してきましたが、それにはいくつかの制限があることがわかりました。 対処する必要があった設計:

  1. アクションはウィジェット階層の外部から呼び出すことができませんでした。 この例には、コマンドのスクリプトの処理が含まれます。 一部のアンドゥ アーキテクチャと一部のコントローラ アーキテクチャ。

  2. ショートカットキーからへのマッピングIntentそしてそしてActionデータ構造が複雑であるため、必ずしも明確ではありませんでした。 マッピングされたLogicalKeySet =>Intent、そしてLocalKey=>ActionFactory。新しいマッピングはまだLogicalKeySetIntentしかしその後、マッピングされますType(Intentと入力してください)Actionこれはより直接的であり、 インテントのタイプがマッピングに記述されているため、読み取り可能です。

  3. アクションのキー バインドが別の部分にあった場合、 ウィジェット階層では、常に可能であるとは限りませんでした。Intentかどうかを決定するために必要な状態にアクセスするため。 インテント/アクションを有効にするかどうかを指定する必要があります。

これらの問題に対処するために、API にいくつかの重要な変更を加えました。 アクションのマッピングがより直感的になりました。 有効なインターフェイスは、Actionクラス。 いくつかの不要な引数がActioninvokeメソッドとそのコンストラクター、およびアクションが許可されました 呼び出しメソッドから結果を返します。 アクションはジェネリックスに作成され、次のタイプを受け入れます。Intent彼らは扱います、そしてLocalKeysもはや識別には使用されませんでした 実行するアクションとそのタイプIntent代わりに使用されます。

これらの変更の大部分は、次の PR で行われました。アクションAPIの改訂とAction.enabled を有効にする 代わりに isEnabled(Intent インテント)、そして に詳しく説明されていますデザイン 博士

変更内容の説明

上記の問題に対処するために加えられた変更は次のとおりです。

  1. Map<LocalKey, ActionFactory>に与えられたのはActionsウィジェット 今はMap<Type, Action<Intent>>(タイプはインテントのタイプです) アクションに渡されます)。
  2. isEnabledメソッドはから移動されましたIntentクラスへActionクラス。
  3. FocusNodeに対する議論Action.invokeActions.invokeメソッドが削除されました。
  4. アクションを呼び出しても、Action
  5. LocalKeyに対する議論Intentコンストラクターが削除されました。
  6. LocalKeyに対する議論CallbackAction取り除かれた。
  7. Actionクラスはジェネリックになりました (Action<T extends Intent>) 良い方向へ タイプセーフティ。
  8. OnInvokeCallbackによって使われたCallbackActionもう必要ありませんFocusNode口論。
  9. ActionDispatcher.invokeAction署名は受け入れられないように変更されました オプションFocusNodeですが、代わりにオプションのBuildContext
  10. LocalKey静的定数 (慣例により名前付きキー)Actionサブクラスは削除されました。
  11. Action.invokeActionDispatcher.invokeActionメソッドが返されるようになりました アクションを呼び出した結果Object
  12. Actionクラスの状態変化をリッスンできるようになりました。
  13. ActionFactorytypedef は使用されなくなったため、削除されました。

アナライザーの故障例

以下に、次のような場合に発生する可能性のあるアナライザーの障害の例をいくつか示します。 Actions API の古い使用法が問題の原因である可能性があります。詳細 エラーの内容は異なる可能性があり、これらが原因で他の障害が発生する可能性があります。 変化します。

error: MyActionDispatcher.invokeAction' ('bool Function(Action<Intent>, Intent, {FocusNode focusNode})') isn't a valid override of 'ActionDispatcher.invokeAction' ('Object Function(Action<Intent>, Intent, [BuildContext])'). (invalid_override at [main] lib/main.dart:74)

error: MyAction.invoke' ('void Function(FocusNode, Intent)') isn't a valid override of 'Action.invoke' ('Object Function(Intent)'). (invalid_override at [main] lib/main.dart:231)

error: The method 'isEnabled' isn't defined for the type 'Intent'. (undefined_method at [main] lib/main.dart:97)

error: The argument type 'Null Function(FocusNode, Intent)' can't be assigned to the parameter type 'Object Function(Intent)'. (argument_type_not_assignable at [main] lib/main.dart:176)

error: The getter 'key' isn't defined for the type 'NextFocusAction'. (undefined_getter at [main] lib/main.dart:294)

error: The argument type 'Map<LocalKey, dynamic>' can't be assigned to the parameter type 'Map<Type, Action<Intent>>'. (argument_type_not_assignable at [main] lib/main.dart:418)

移行ガイド

既存のコードを更新するには大幅な変更が必要な領域 新しい API に。

事前定義されたアクションのアクションマッピング

アクション マップを更新するには、Actionsのウィジェット Flutter の事前定義されたアクション:ActivateActionSelectAction、 以下をせよ:

  • の引数の型を更新します。actions口論
  • 特定のインスタンスを使用するIntentのクラスShortcutsマッピングではなく、Intent(TheAction.key)実例。

移行前のコード:

class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): Intent(ActivateAction.key),
      },
      child: Actions(
        actions: <LocalKey, ActionFactory>{
          Activate.key: () => ActivateAction(),
        },
        child: Container(),
      )
    );
  }
}

移行後のコード:

class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): ActivateIntent,
      },
      child: Actions(
        actions: <Type, Action<Intent>>{
          ActivateIntent: ActivateAction(),
        },
        child: Container(),
      )
    );
  }
}

カスタムアクション

カスタム アクションを移行するには、LocalKeys定義したものを次のものに置き換えますIntentサブクラス、 引数の型をactionsの議論Actionsウィジェット。

移行前のコード:

class MyAction extends Action {
  MyAction() : super(key);

  /// The [LocalKey] that uniquely identifies this action to an [Intent].
  static const LocalKey key = ValueKey<Type>(RequestFocusAction);

  @override
  void invoke(FocusNode node, MyIntent intent) {
    // ...
  }
}

class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): Intent(MyAction.key),
      },
      child: Actions(
        actions: <LocalKey, ActionFactory>{
          MyAction.key: () => MyAction(),
        },
        child: Container(),
      )
    );
  }
}

移行後のコード:

// You may need to create new Intent subclasses if you used
// a bare LocalKey before.
class MyIntent extends Intent {
  const MyIntent();
}

class MyAction extends Action<MyIntent> {
  @override
  Object invoke(MyIntent intent) {
    // ...
  }
}

class MyWidget extends StatelessWidget {
  // ...
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent> {
        LogicalKeySet(LogicalKeyboardKey.enter): MyIntent,
      },
      child: Actions(
        actions: <Type, Action<Intent>>{
          MyIntent: MyAction(),
        },
        child: Container(),
      )
    );
  }
}

カスタムActionsIntents引数付き

インテント引数を使用するアクションまたは状態を保持するアクションを更新するには、 の引数を変更する必要がありますinvoke方法。 以下の例では、コードは アクションインスタンスの一部としてインテント内の引数を指定します。 これは、古い設計には新しいインスタンスがあるためです。 実行されるたびに作成されるアクションの、 そして結果として得られるアクションは、ActionDispatcher状態を記録するため。

以下の移行後のコードの例では、 新しいMyAction結果として状態を返します 電話のinvoke、新しいインスタンスは作成されないため、 呼び出しごとに。この状態は呼び出し元に返されます。Actions.invoke、 またActionDispatcher.invokeAction、 アクションの呼び出し方法に応じて異なります。

移行前のコード:

class MyIntent extends Intent {
  const MyIntent({this.argument});

  final int argument;
}

class MyAction extends Action {
  MyAction() : super(key);

  /// The [LocalKey] that uniquely identifies this action to an [Intent].
  static const LocalKey key = ValueKey<Type>(RequestFocusAction);

  int state;

  @override
  void invoke(FocusNode node, MyIntent intent) {
    // ...
    state = intent.argument;
  }
}

移行後のコード:

class MyIntent extends Intent {
  const MyIntent({this.argument});

  final int argument;
}

class MyAction extends Action<MyIntent> {
  @override
  int invoke(Intent intent) {
    // ...
    return intent.argument;
  }
}

タイムライン

リリースされたバージョン: 1.18
安定版リリース: 1.20

参考文献

API ドキュメント:

  • Action
  • ActionDispatcher
  • Actions
  • Intent
  • Shortcuts

関連する問題:

  • 問題 53276

関連する PR:

  • アクションAPIの改訂
  • 代わりに Action.enabled を isEnabled(Intent インテント) にします